/**
 * 
 */
package com.starview.star.compiler.type;

import java.util.Map;

import com.starview.platform.data.type.DisplayType;
import com.starview.platform.data.type.Freshen;
import com.starview.platform.data.type.IType;
import com.starview.platform.data.type.Location;
import com.starview.platform.data.type.Quantifier;
import com.starview.platform.data.type.Subsume;
import com.starview.platform.data.type.TypeConstraintException;
import com.starview.platform.data.type.TypeInterface;
import com.starview.platform.data.type.TypeUtils;
import com.starview.platform.data.type.TypeVar;
import com.starview.star.compiler.ErrorReport;
import com.starview.star.compiler.canonical.Variable;
import com.starview.star.compiler.standard.StandardNames;
import com.starview.star.compiler.util.AccessMode;
import com.starview.star.compiler.util.Pair;
import com.starview.star.compiler.util.PrettyPrintDisplay;
import com.starview.star.compiler.util.StringUtils;

@SuppressWarnings("serial")
public class VarInfo implements DictInfo
{
  private AccessMode access;
  private final Variable var;
  private boolean initialized;
  private final IType face;

  VarInfo(Variable ref, AccessMode readOnly, boolean initialized)
  {
    this.access = readOnly;
    this.var = ref;
    this.initialized = initialized;
    this.face = new TypeVar();
  }

  @Override
  public Variable getVariable()
  {
    return var;
  }

  @Override
  public AccessMode getAccess()
  {
    return access;
  }

  public void setAccess(AccessMode access)
  {
    this.access = access;
  }

  @Override
  public boolean isInitialized()
  {
    return initialized;
  }

  public void setInitialized(boolean initialized)
  {
    this.initialized = initialized;
  }

  @Override
  public Location getLoc()
  {
    return var.getLoc();
  }

  @Override
  public String getName()
  {
    return var.getName();
  }

  @Override
  public IType getType()
  {
    return var.getType();
  }

  public IType getFace()
  {
    return face;
  }

  public IType localType(Location loc, String field, Dictionary dict, ErrorReport errors)
  {
    IType type = TypeUtils.deRef(getType());

    if (TypeUtils.isTypeVar(face) && !TypeUtils.isTypeVar(type)) {
      try {
        IType face = TypeUtils.interfaceOfType(loc, type, dict);
        Pair<IType, Map<String, Quantifier>> fface = Freshen.freshen(face, AccessMode.readOnly, AccessMode.readWrite);
        Subsume.subsume(this.face, fface.left, loc, dict);
      } catch (TypeConstraintException e) {
        errors.reportError(StringUtils.msg("cannot determine interface of ", getVariable()), loc);
        return new TypeVar();
      }
    }

    if (TypeUtils.isTypeVar(face)) {
      IType fieldType = new TypeVar(field);
      try {
        TypeUtils.addTypeConstraint((TypeVar) face, loc, field, fieldType, dict, false);
      } catch (TypeConstraintException e) {
        errors.reportError("type " + field + " from " + getVariable() + " is not valid here", loc);
      }
      return fieldType;
    }
    return TypeUtils.getInterfaceMemberType(face, field);
  }

  public IType typeOfField(Location loc, String field, Dictionary dict, ErrorReport errors)
  {
    IType type = TypeUtils.deRef(getType());

    if (TypeUtils.isTypeInterface(type))
      return TypeUtils.getInterfaceField(type, field);
    else if (TypeUtils.isTypeVar(type)) {
      IType fieldType = new TypeVar();
      try {
        TypeUtils.addFieldConstraint((TypeVar) type, loc, field, fieldType, dict, false);
      } catch (TypeConstraintException e) {
        errors.reportError("type " + field + " from " + getVariable() + " is not valid here", loc);
      }
      return fieldType;
    } else if (TypeUtils.isTypeVar(face)) {
      try {
        IType face = Freshen.freshenForUse(TypeUtils.interfaceOfType(loc, type, dict));

        Subsume.subsume(this.face, face, loc, dict);

        return TypeUtils.getInterfaceField(face, field);
      } catch (TypeConstraintException e) {
        errors.reportError(StringUtils.msg("cannot determine interface of ", getVariable()), loc);
        return new TypeVar();
      }
    } else
      return ((TypeInterface) TypeUtils.deRef(face)).getFieldType(field);
  }

  @Override
  public void prettyPrint(PrettyPrintDisplay disp)
  {
    var.prettyPrint(disp);

    if (!initialized)
      disp.append("[u]");

    disp.appendWord(StandardNames.HAS);
    disp.appendWord(StandardNames.TYPE);
    disp.append(" ");
    DisplayType.display(disp, getType());
  }

  @Override
  public String toString()
  {
    PrettyPrintDisplay disp = new PrettyPrintDisplay();
    prettyPrint(disp);
    return disp.toString();
  }
}